
import wx
import os.path
import sys
import re

import xp3
import archive
from archive import Entry

# {{{ entry


class EntryDir:
	def __init__(self, name):
		self.name = name
		self.entries = []
		self.mapping = {}
	def append(self, entry):
		path = getattr(entry, 'basename', entry.name)
		k = os.path.dirname(path)
		if k:
			entry.basename = os.path.basename(path)
			if k in self.mapping:
				self.mapping[k].append(entry)
			else:
				x = EntryDir(os.path.dirname(path))
				x.append(entry)
				self.mapping[k] = x
				self.entries.append(x)
		else:
			self.entries.append(entry)

def entries_to_tree(entries):
	if isinstance(entries, Entry):
		return entries
	dir = EntryDir('root')
	for e in entries:
		dir.append(e)
	return dir

# }}}

def GenerateTempFile(content, name):
	#TODO: use a better approach...
	import tempfile
	import os
	fd, path = tempfile.mkstemp(suffix = '-'+name, prefix = 'xp3-browser-')
	fp = os.fdopen(fd, 'wb')
	fp.write(content)
	fp.close()
	return path

def RunFile(path):
	import subprocess
	subprocess.call(['cmd', '/c', path])

def tlg_to_png(path):
	tlg_path = path
	png_path = path + '.png'
	try:
		import tlg
		t = tlg.read_tlg(tlg_path)
		try:
			tlg.write_png(t, png_path)
		finally:
			tlg.delete_tlg(t)
	except Exception, e:
		return tlg_path
	else:
		return png_path

def OpenEntry(entry):
	# TODO:
	# 1. extract files to a temp folder, instead of lonely files
	# 2. don't redecode
	path = re.sub(r'[/\\()]', '-', entry.name)
	path = xp3.encode_path(path)
	if isinstance(path, unicode):
		path = path.encode(sys.getfilesystemencoding())
	#path = xp3.force_encode_path(path)
	path = GenerateTempFile(entry.decode(), path)
	if re.search(r'\.tlg$', path, re.I):
		path = tlg_to_png(path)
	RunFile(path)
	# TODO: delete temp file

# {{{ tree
class XP3Tree(wx.TreeCtrl):
	def __init__(self, parent):
		style = (wx.TR_DEFAULT_STYLE
				 #wx.TR_HAS_BUTTONS
				 | wx.TR_EDIT_LABELS
				 #| wx.TR_MULTIPLE
				 | wx.TR_HIDE_ROOT
				 | wx.TR_HAS_VARIABLE_ROW_HEIGHT)
		wx.TreeCtrl.__init__(self, parent, style=style)
		self.root = self.AddRoot("You can't see me:)")
		self.root_data = None

		#self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
		self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnRightClick)
		self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnDoubleClick)
	
	def Clear(self):
		#self.DeleteChildren(self.root)
		self.DeleteAllItems() # XXX: should I recreate root?

	def Append(self, entry):
		self.AppendItem(self.root, entry.name)

	def AppendEntries(self, entries):
		dir = entries_to_tree(entries)
		name = os.path.basename(self.path)
		if name.find('.') != -1:
			name = name[:name.rfind('.')]
		dir.name = name
		self.root_data = dir
		self.AppendDir(self.root, dir)

	def AppendDir(self, node, dir):
		for e in dir.entries:
			if isinstance(e, EntryDir):
				n = self.AppendItem(node, e.name)
				self.AppendDir(n, e)
			else:
				n = self.AppendItem(node, getattr(e, 'basename', e.name))
			self.SetPyData(n, e)

	def LoadArchive(self, path):
		self.path = None
		self.Clear()
		self.path = path
		entries = archive.Entries(path)
		self.AppendEntries(entries)

	def OnDoubleClick(self, event):
		item = event.GetItem()
		entry = self.GetPyData(item)
		if isinstance(entry, Entry):
			OpenEntry(entry)
		else:
			event.Skip()

	def OnRightClick(self, event):
		item = event.GetItem()
		item = self.GetPyData(item)
		if isinstance(item, Entry) or isinstance(item, EntryDir):
			self.ShowContextMenu(item)

	def ShowContextMenu(self, item):
		def export(event):
			if isinstance(item, Entry):
				self.ExportEntryDialog(item)
			elif isinstance(item, EntryDir):
				self.ExportFolderDialog(item)

		export_id = wx.NewId()
		self.Bind(wx.EVT_MENU, export, id=export_id)

		menu = wx.Menu()
		
		menu.Append(export_id, "Export...")
		
		if isinstance(item, Entry):
			def browse(event):
				OpenEntry(item)
			open_id = wx.NewId()
			self.Bind(wx.EVT_MENU, browse, id=open_id)
			menu.Append(open_id, "Open")

		self.PopupMenu(menu)
		menu.Destroy()

	def ExportEntryDialog(self, entry):
		name = os.path.basename(entry.name)
		dot = name.rfind('.')
		if dot != -1:
			ext = name[dot+1:]
			name = name[:dot]
		else:
			ext = None
		path = SaveFile(self, ext, ext, name=name)
		if path:
			self.ExportEntry(entry, path)

	def ExportEntry(self, entry, path):
		with open(path, 'wb') as x:
			x.write(entry.decode())

	def ExportFolderDialog(self, dir):
		if not dir:
			return
		name = dir.name
		path = SaveFile(self, None, None, name=name)
		if path:
			self.ExportFolder(dir, path)

	def ExportFolder(self, dir, path):
		os.mkdir(path)
		for e in dir.entries:
			if isinstance(e, Entry):
				self.ExportEntry(e, os.path.join(path, os.path.basename(e.name)))
			else:
				self.ExportFolder(e, os.path.join(path, e.name))

# }}}

# {{{ open file dialog

def OpenFile(parent, types, title = "Open File"):
	path = None

	def join_exts(exts):
		if isinstance(exts, basestring):
			return '*.'+exts
		else:
			return ';'.join('*.'+ext for ext in exts)
	wildcard = "%s|All files (*.*)|*.*" % '|'.join('%s (%s)|%s' % (type, join_exts(ext), join_exts(ext)) for type, ext in types)

	dialog = wx.FileDialog(
			parent, message=title, defaultDir="",
			defaultFile="", wildcard=wildcard, style=wx.OPEN
			)
	dialog.SetFilterIndex(0)
	if dialog.ShowModal() == wx.ID_OK:
		path = dialog.GetPath()
	dialog.Destroy()

	return path

def OverwriteConfirm(parent, path, title = "Save Confirmation"):
	dialog = wx.MessageDialog(parent, 'Do you want to overwrite %s?' % path,
			title,
			wx.YES_NO | wx.ICON_WARNING
			)
	selection = dialog.ShowModal()
	dialog.Destroy()
	return selection == wx.ID_YES

def SaveFile(parent, fileType, fileExt, title = "Save file as ...", name=''):
	path = None

	if fileType and fileExt:
		wildcard = "%s (*.%s)|*.%s|All files (*.*)|*.*" % (fileType, fileExt, fileExt)
	else:
		wildcard = "All files (*.*)|*.*"
	dialog = wx.FileDialog(
			parent, message=title, defaultDir="",
			defaultFile=name, wildcard=wildcard, style=wx.SAVE
			)
	dialog.SetFilterIndex(0)
	if dialog.ShowModal() == wx.ID_OK:
		path = dialog.GetPath()
	dialog.Destroy()
	if not path:
		return

	# Give a warning if file exists
	import os.path
	if os.path.exists(path):
		if not OverwriteConfirm(parent, path):
			return

	return path

# }}}

class MainFrame(wx.Frame):
	def __init__(self):
		wx.Frame.__init__(self, None, -1, "XP3 Browser", size=(800, 600))

		#XXX: why can't I remove it?
		self.Bind(wx.EVT_SIZE, self.OnSize)

		self.splitter = wx.SplitterWindow(self, style=wx.BORDER_NONE)

		self.tree = XP3Tree(self.splitter)
		self.detailsPanel = wx.TextCtrl(self.splitter, style=wx.TE_MULTILINE|wx.TE_RICH2)

		self.splitter.SetMinimumPaneSize(20)
		self.splitter.SplitVertically(self.tree, self.detailsPanel, 180)

		sizer = wx.BoxSizer()
		sizer.Add(self.splitter, proportion=1, flag=wx.EXPAND)
		self.SetSizer(sizer) 

		self.Bind(wx.EVT_TREE_SEL_CHANGED, self.LoadEntry, self.tree)

		self.UseMenuBarFrom(self.menuData())
		self.UseToolBar()

	def LoadEntry(self, event):
		item = event.GetItem()
		path = self.tree.path
		if not item:
			return
		if not path:
			return
		data = self.tree.GetPyData(item)
		if isinstance(data, Entry):
			if data.size > 1024*1024*1:
				self.detailsPanel.SetValue('too big')
			else:
				x = data.decode()
				if re.search(r'^\xff\xfe', x):
					x = x.decode('utf-16le')[1:]
				elif re.search(r'(\.ks|\.tjs|\.txt|\.fcf)$', data.name):
					x = x.decode('cp932')
				else:
					x = repr(x)
				self.detailsPanel.SetValue(x)

	# {{{ Menu
	def menuData(self):
		return [
				("&File", (
					("&Open Archive\tCtrl+O", "Open Archive", self.OnOpen),
					("&Save\tCtrl+S", "Save Project", self.OnSave),
					("Save &As", "Save Project As...", self.OnSaveAs),
					("", "", ""),
					("E&xit", "Exit", self.OnExit),
					)),
				("&View", 
					(
					)),
				("&Help", (
					("&About", "", self.OnAbout),
					)),
				]

	def UseMenuBarFrom(self, data):
		menuBar = wx.MenuBar()
		for eachMenuData in data:
			menuLabel = eachMenuData[0]
			menuItems = eachMenuData[1]
			menuBar.Append(self.CreateMenu(menuItems), menuLabel)
		self.SetMenuBar(menuBar)

	def CreateMenu(self, menuData):
		menu = wx.Menu()
		for eachItem in menuData:
			if len(eachItem) == 2:
				label = eachItem[0]
				subMenu = self.CreateMenu(eachItem[1])
				menu.AppendMenu(wx.NewId(), label, subMenu)
			else:
				self.CreateMenuItem(menu, *eachItem)
		return menu

	def CreateMenuItem(self, menu, label, status, handler, enabled = None, icon = None, icon2 = None):
		kind = wx.ITEM_NORMAL

		if not label:
			menu.AppendSeparator()
			return
		menuItem = wx.MenuItem(menu, wx.NewId(), label, status, kind)
		if icon:
			menuItem.SetBitmaps(icon, icon2)
		menu.AppendItem(menuItem)
		if enabled == False:
			menuItem.Enable(enabled)

		self.Bind(wx.EVT_MENU, handler, menuItem)

	def UseMenuBar(self):
		self.UseMenuBarFrom(self.menuData())
	# }}}

	# {{{ toolbar
	def InitIcons(self):
		if sys.platform == 'linux2':
			iconSize = (24, 24)
		else:
			iconSize = (16, 16)
		self.newIcon = wx.ArtProvider_GetBitmap(wx.ART_NEW, wx.ART_OTHER, iconSize)
		self.newIconOff = wx.ArtProvider_GetBitmap(wx.ART_ERROR, wx.ART_OTHER, iconSize) #TODO: add correct icon
		self.openIcon = wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, iconSize)
		self.openIconOff = wx.ArtProvider_GetBitmap(wx.ART_ERROR, wx.ART_OTHER, iconSize) #TODO: add correct icon
		self.saveIcon = wx.ArtProvider_GetBitmap(wx.ART_FILE_SAVE, wx.ART_OTHER, iconSize)
		self.saveIconOff = wx.ArtProvider_GetBitmap(wx.ART_ERROR, wx.ART_OTHER, iconSize) #TODO: add correct icon
		self.saveAsIcon = wx.ArtProvider_GetBitmap(wx.ART_FILE_SAVE_AS, wx.ART_OTHER, iconSize)
		self.saveAsIconOff = wx.ArtProvider_GetBitmap(wx.ART_ERROR, wx.ART_OTHER, iconSize) #TODO: add correct icon
	
	def UseToolBar(self):
		self.InitIcons()

		toolbar = self.CreateToolBar()

		open = toolbar.AddSimpleTool(wx.NewId(), self.openIcon, "Open")
		self.Bind(wx.EVT_MENU, self.OnOpen, open)

		save = toolbar.AddSimpleTool(wx.NewId(), self.saveIcon, "Save")
		self.Bind(wx.EVT_MENU, self.OnSave, save)

		saveAs = toolbar.AddSimpleTool(wx.NewId(), self.saveAsIcon, "Save As")
		self.Bind(wx.EVT_MENU, self.OnSaveAs, saveAs)

		toolbar.AddSeparator()

		self.toolbar = toolbar

		toolbar.Realize()

	def createTool(self, toolbar, label, icon1, icon2):
		return toolbar.AddLabelTool(wx.NewId(), label, icon1, icon2, shortHelp=label)

	# }}}

	# {{{ event handlers
	def OnOpen(self, event):
		#path = OpenFile(self, [('XP3 Archive', 'xp3'), ('NSA Archive', 'nsa')], "Open XP3/NSA Archive")
		path = OpenFile(self, [('XP3/NSA Archives', ('xp3', 'nsa'))], "Open XP3/NSA Archive")
		if path:
			self.tree.LoadArchive(path)
			self.SetTitle(os.path.basename(path) + ' - XP3 Browser')
			self.detailsPanel.SetValue('')

	def OnSave(self, event):
		self.tree.ExportFolderDialog(self.tree.root_data)

	def OnSaveAs(self, event):
		self.OnSave(event)


	def OnExit(self, event):
		self.Close()

	def OnClose(self, event):
		self.Destroy()

	def OnAbout(self, event):
		todo = '''\
				TODO:
				Modify archive
				Open multiple archives'''
		import textwrap
		todo = textwrap.dedent(todo)
		dialog = wx.MessageDialog(self, todo,
				'About XP3 Browser',
				wx.OK | wx.ICON_INFORMATION
				)
		selection = dialog.ShowModal()
		dialog.Destroy()

	def OnSize(self, event):
		event.Skip()
	
	# }}}

def Main():
	app = wx.PySimpleApp()
	frame = MainFrame()
	frame.Center()
	frame.Show(True)
	app.MainLoop()


if __name__ == '__main__':
	Main()

# vim: foldmethod=marker:
